﻿/*
 Microsoft Public License (MS-PL)
 --------------------------------
 * * This license governs use of the accompanying software. If you use the software, you
accept this license. If you do not accept the license, do not use the software.

1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.

2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.

3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;

namespace SD.CoreLibrary.CronExpressionDescripter
{
    public class SegmentDescription : INotifyPropertyChanged
    {
        // boiler-plate
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }

        protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(selectorExpression);
            return true;
        }


        public string OriginalExpression { get; protected set; }

        public bool EmptyFlag
        {
            get { return IsEmptySegment(); }
        }

        private bool _everyFlag;
        public bool EveryFlag
        {
            get { return _everyFlag; }
            set { SetField(ref _everyFlag, value, () => EveryFlag); }
        }

        public ObservableCollection<ValueDescription> Values { get; protected set; }
        public ObservableCollection<RangeDescription> Ranges { get; protected set; }

        protected ICronSegmentDescription FunctorDescription { get; set; }

        public SegmentDescription()
        {
            Values = new ObservableCollection<ValueDescription>();
            Ranges = new ObservableCollection<RangeDescription>();
        }

        public SegmentDescription(ICronSegmentDescription functorDescription)
            : this()
        {
            FunctorDescription = functorDescription;
        }

        public virtual string GetStringExpression()
        {
            string resp = "";
            if (EveryFlag)
                resp = "*";
            else if (EveryFlag)
            {
                return resp;
            }
            return resp;
        }

        public SegmentDescription(string expression)
            : this()
        {
            OriginalExpression = expression;
            ParseSegment(OriginalExpression);
        }

        protected virtual bool IsEmptySegment()
        {
            return !EveryFlag && Values.Count != 0 && Ranges.Count != 0;
        }

        protected virtual int ParseSegment(string expression, string previousPart = "")
        {
            int resp = -1;
            int start = -1;
            int end = -1;
            int increment = -1;
            int value = -1;

            // Passed empty string
            if (string.IsNullOrEmpty(expression) && string.IsNullOrEmpty(previousPart))
            {
                return resp;
            }

            // Passed only * as first parse phase
            if (expression == "*" & string.IsNullOrEmpty(previousPart))
            {
                EveryFlag = true;
                return resp;
            }

            // Expression contains list of values pattern
            if (expression.Contains(","))
            {
                string[] segments = expression.Split(',');

                foreach (string segment in segments)
                {
                    ParseSegment(segment);
                }
            }
            // Expression contains Increment pattern
            else if (expression.Contains("/"))
            {
                string[] segments = expression.Split('/');
                if (!segments[1].Contains("*"))
                {
                    previousPart = segments[1];
                    ParseSegment(segments[0], previousPart);
                }
                else
                    throw new FormatException("Error in Cron expression");
            }
            // Expression contain Range pattern
            else if (expression.Contains("-"))
            {
                string[] segments = expression.Split('-');
                start = int.Parse(segments[0]);
                end = int.Parse(segments[1]);
                if (!string.IsNullOrEmpty(previousPart))
                    increment = int.Parse(previousPart);
                RangeDescription range = new RangeDescription(start, end, increment);
                Ranges.Add(range);
            }
            // Expression is a single value
            else
            {
                bool ok = int.TryParse(expression, out value);
                bool everyFlag = expression == "*";
                if (!ok && everyFlag)
                {
                    EveryFlag = true;
                    increment = int.Parse(previousPart);
                    ValueDescription val = new ValueDescription(everyFlag, value, increment);
                    Values.Add(val);
                }
                else if (ok)
                {
                    resp = value;
                    if (!string.IsNullOrEmpty(previousPart))
                        increment = int.Parse(previousPart);
                    if (resp == 0 || resp == 1)
                        EveryFlag = true;
                    ValueDescription val = new ValueDescription(everyFlag, value, increment);
                    Values.Add(val);
                }
                else
                {
                    throw new FormatException("Houston we have a problem !!! CRON String not valid");
                }
            }
            return resp;
        }

        public virtual string GetSegmentDescription()
        {
            return GetSegmentDescription(FunctorDescription);
        }

        public virtual string GetSegmentDescription(string allDescription,
                                                    Func<string, string> singleItemDescription,
                                                    Func<string, string> intervalDescriptionFormat,
                                                    Func<string, string, string> betweenDescriptionFormat,
                                                    Func<string, string> descriptionFormat)
        {
            string description = "";
            List<string> valDescriptions = new List<string>();
            List<string> rangDescriptions = new List<string>();
            if (EveryFlag)
                description = allDescription;
            else
            {
                if (Values.Count != 0)
                {
                    foreach (ValueDescription value in Values)
                    {
                        string valueDescription = "";
                        if (value.Increment != -1)
                        {
                            valueDescription += descriptionFormat(singleItemDescription(value.Value.ToString()));
                            //valueDescription += getSingleItemDescription(value.Value.ToString());
                            valueDescription += " " + intervalDescriptionFormat(value.Increment.ToString());
                        }
                        else
                            valueDescription += descriptionFormat(singleItemDescription(value.Value.ToString()));
                        valDescriptions.Add(valueDescription);
                    }
                }
                if (Ranges.Count != 0)
                {
                    foreach (RangeDescription range in Ranges)
                    {
                        string rangeDescription = betweenDescriptionFormat(range.Start.ToString(), range.End.ToString());
                        if (range.Increment != -1)
                            rangeDescription += " " + intervalDescriptionFormat(range.Increment.ToString());
                        rangDescriptions.Add(rangeDescription);
                    }
                }

                if (valDescriptions.Count != 0)
                    description += (string.Join(" and ", valDescriptions));
                if (rangDescriptions.Count != 0)
                {
                    if (!string.IsNullOrEmpty(description))
                        description += ", and ";
                    description += string.Join(", and ", rangDescriptions);
                }
            }
            return description;
        }

        public virtual string GetSegmentDescription(ICronSegmentDescription functorsDescription)
        {
            string resp = GetSegmentDescription(functorsDescription.AllDescription,
                                                functorsDescription.SingleItemDescription,
                                                functorsDescription.IntervalDescriptionFormat,
                                                functorsDescription.BetweenDescriptionFormat,
                                                functorsDescription.DescriptionFormat);
            return resp;
        }

        public virtual string CronExpression
        {
            get
            {
                string resp = "";
                if (EveryFlag && Values.Count == 0)
                    resp = "*";
                else
                {
                    string desc = "";
                    List<string> exp = new List<string>();
                    foreach (ValueDescription valueDescription in Values)
                    {
                        if (valueDescription.EveryFlag)
                            desc += "*";
                        else
                            desc += valueDescription.Value;
                        if (valueDescription.Increment != -1)
                            desc += "/" + valueDescription.Increment;
                        exp.Add(desc);
                        desc = string.Empty;
                    }
                    foreach (RangeDescription rangeDescription in Ranges)
                    {
                        desc += string.Format("{0}-{1}", rangeDescription.Start, rangeDescription.End);
                        if (rangeDescription.Increment != -1)
                            desc += "/" + rangeDescription.Increment;
                        exp.Add(desc);
                        desc = string.Empty;
                    }
                    resp = string.Join(",", exp);
                }
                if (string.IsNullOrEmpty(resp))
                    resp = "0";
                return resp;
            }
        }
    }

    public class LastSegmentDescription : SegmentDescription
    {
        public LastSegmentDescription()
            : base()
        {
        }

        public LastSegmentDescription(string expression)
            : base(expression)
        {
        }

        private bool _lastFlag;
        public bool LastFlag
        {
            get { return _lastFlag; }
            set { SetField(ref _lastFlag, value, () => LastFlag); }
        }

        private int _lastValue;
        public int LastValue
        {
            get { return _lastValue; }
            set { SetField(ref _lastValue, value, () => LastValue); }
        }

        private bool _noSpecificFlag;
        public bool NoSpecificFlag
        {
            get { return _noSpecificFlag; }
            set { SetField(ref _noSpecificFlag, value, () => NoSpecificFlag); }
        }


        protected override int ParseSegment(string expression, string previousPart = "")
        {
            int resp = -1;
            if (expression.Contains("L"))
            {
                LastFlag = true;
                expression = expression.Replace("L", "");
                LastValue = base.ParseSegment(expression);
                Values = new ObservableCollection<ValueDescription>();
                Ranges = new ObservableCollection<RangeDescription>();
            }
            else if (expression.Contains("?"))
            {
                NoSpecificFlag = true;
                Values = new ObservableCollection<ValueDescription>();
                Ranges = new ObservableCollection<RangeDescription>();
            }
            else
                resp = base.ParseSegment(expression, previousPart);
            return resp;
        }

        protected override bool IsEmptySegment()
        {
            return base.IsEmptySegment() && !LastFlag;
        }


    }

    public class WeekSegmentDescription : LastSegmentDescription
    {
        public WeekSegmentDescription()
            : base()
        {
        }

        public WeekSegmentDescription(string expression)
            : base(expression)
        {
        }

        private bool _weekDayFlag;
        public bool WeekDayFlag
        {
            get { return _weekDayFlag; }
            set { SetField(ref _weekDayFlag, value, () => WeekDayFlag); }
        }


        private int _weekDayValue;
        public int WeekDayValue
        {
            get { return _weekDayValue; }
            set { SetField(ref _weekDayValue, value, () => WeekDayValue); }
        }

        protected override int ParseSegment(string expression, string previousPart = "")
        {
            int resp = -1;
            if (expression.Contains("W"))
            {
                WeekDayFlag = true;
                expression = expression.Replace("W", "");
                WeekDayValue = base.ParseSegment(expression, previousPart);
                Values = new ObservableCollection<ValueDescription>();
                Ranges = new ObservableCollection<RangeDescription>();
            }
            else
                resp = base.ParseSegment(expression, previousPart);
            return resp;
        }

        protected override bool IsEmptySegment()
        {
            return base.IsEmptySegment() && !WeekDayFlag;
        }

        //public override string GetSegmentDescription(string allDescription,
        //                                    Func<string, string> getSingleItemDescription,
        //                                    Func<string, string> getIntervalDescriptionFormat,
        //                                    Func<string, string, string> getBetweenDescriptionFormat,
        //                                    Func<string, string> getDescriptionFormat)
        //{
        //    string description = "";
        //    if (LastFlag && WeekDayFlag)
        //    {
        //        description = ", on the last weekday of the month";
        //    }
        //    else
        //        if (LastFlag)
        //            description = ", on the last day of the month";

        //        else
        //            if (WeekDayFlag)
        //            {
        //                int dayNumber = WeekDayValue;
        //                string dayString = dayNumber == 1
        //                                       ? "first weekday"
        //                                       : String.Format("weekday nearest day {0}", dayNumber);
        //                description = String.Format(", on the {0} of the month", dayString);
        //            }
        //            else
        //            {
        //                description = base.GetSegmentDescription(
        //                    ", every day",
        //                    (s => s),
        //                    (s => s == "1" ? ", every day" : ", every {0} days"),
        //                    ((s, p) => string.Format(", between day {0} and {1} of the month", s, p)),
        //                    (s => string.Format(", on day {0} of the month", s)));
        //            }

        //    return description;
        //}

    }

    public class NthSegmentDescription : LastSegmentDescription
    {
        public NthSegmentDescription()
            : base()
        {
        }

        public NthSegmentDescription(string expression)
            : base(expression)
        {
        }

        private int _nthValue;
        public int NthValue
        {
            get { return _nthValue; }
            set { SetField(ref _nthValue, value, () => NthValue); }
        }

        private bool _nthFlag;
        public bool NthFlag
        {
            get { return _nthFlag; }
            set { SetField(ref _nthFlag, value, () => NthFlag); }
        }

        private int _weekDayValue;
        public int WeekDayValue
        {
            get { return _weekDayValue; }
            set { SetField(ref _weekDayValue, value, () => WeekDayValue); }
        }

        protected override int ParseSegment(string expression, string previousPart = "")
        {
            int resp = -1;
            if (expression.Contains("#"))
            {
                NthFlag = true;
                string[] segments = expression.Split('#');
                NthValue = int.Parse(segments[1]);
                WeekDayValue = int.Parse(segments[0]);
                Values = new ObservableCollection<ValueDescription>();
                Ranges = new ObservableCollection<RangeDescription>();
            }
            else
                resp = base.ParseSegment(expression, previousPart);
            return resp;
        }

        protected override bool IsEmptySegment()
        {
            return base.IsEmptySegment() && !NthFlag;
        }
    }


    public class ValueBase : INotifyPropertyChanged
    {
        // boiler-plate
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
        protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
        {
            if (selectorExpression == null)
                throw new ArgumentNullException("selectorExpression");
            MemberExpression body = selectorExpression.Body as MemberExpression;
            if (body == null)
                throw new ArgumentException("The body must be a member expression");
            OnPropertyChanged(body.Member.Name);
        }

        protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(selectorExpression);
            return true;
        }

        public ValueBase() : this(false, 0) { }

        public ValueBase(bool everyFlag, int increment)
        {
            EveryFlag = everyFlag;
            Increment = increment;
        }

        private bool _everyFlag;
        public bool EveryFlag
        {
            get { return _everyFlag; }
            set { SetField(ref _everyFlag, value, () => EveryFlag); }
        }

        private int _increment;
        public int Increment
        {
            get { return _increment; }
            set { SetField(ref _increment, value, () => Increment); }
        }
    }

    public class ValueDescription : ValueBase
    {
        public ValueDescription()
            : this(0, 0)
        { }

        public ValueDescription(int value)
            : this(value, 0)
        {
        }

        public ValueDescription(int value, int increment)
            : this(false, increment)
        {
            Value = value;
        }

        public ValueDescription(bool everyFlag, int increment)
            : base(everyFlag, increment)
        {
        }

        public ValueDescription(bool everyFlag, int value, int increment)
            : this(everyFlag, increment)
        {
            Value = value;
        }

        private int _value;
        public int Value
        {
            get { return _value; }
            set { SetField(ref _value, value, () => Value); }
        }
    }

    public class RangeDescription : ValueBase
    {
        public RangeDescription()
            : this(0, 0)
        { }

        public RangeDescription(int start, int end)
            : this(start, end, 0)
        {
        }

        public RangeDescription(int start, int end, int increment)
            : base(false, increment)
        {
            Start = Math.Min(start, end);
            End = Math.Max(start, end);
        }

        private int _start;
        public int Start
        {
            get { return _start; }
            set { SetField(ref _start, value, () => Start); }
        }

        private int _end;
        public int End
        {
            get { return _end; }
            set { SetField(ref _end, value, () => End); }
        }

    }
}
